pub mod rpc_server {
    tonic::include_proto!("config_monitor_a");
    tonic::include_proto!("config_monitor_s");
}
use std::{collections::HashSet, path::PathBuf, sync::Arc};

use tokio::{signal, sync::Mutex};
use tonic::{Request, Response, Status};
#[derive(Debug)]
pub struct RpcServerImpl {
    /// 需要维护的配置文件绝对路径的集合
    config_files: Arc<Mutex<HashSet<String>>>,
    /// 当前进程的pid
    pid: u32,
}

impl RpcServerImpl {
    /// 创建一个新的RpcServerImpl实例
    /// # Returns
    /// 一个新的RpcServerImpl实例
    /// # Note
    /// 创建一个新的RpcServerImpl实例，同时创建一个协程，用于在程序退出时清理audit规则
    pub fn new() -> Self {
        let s = RpcServerImpl {
            config_files: Arc::new(Mutex::new(HashSet::new())),
            pid: std::process::id(),
        };
        // 创建一个协程，在程序退出时清理audit规则
        // todo 如何优雅的退出?
        let config_files = s.config_files.clone();
        tokio::spawn(async move {
            signal::ctrl_c().await.unwrap();
            println!("received ctrl-c, cleaning up...");
            // 删除所有的审计规则
            config_files.lock().await.iter().for_each(|file_path| {
                let rule_key = Self::AUDIT_RULE_KEY_PREFIX.to_owned() + file_path;
                std::process::Command::new("sudo")
                    .arg("auditctl")
                    .arg("-W")
                    .arg(file_path)
                    .arg("-F")
                    .arg("perm=wa")
                    .arg("-F")
                    .arg(format!("pid!={}", s.pid))
                    .arg("-F")
                    .arg(format!("key={}", rule_key))
                    .output()
                    .unwrap();
            });
            //exit(0);
            std::process::exit(0);
        });
        s
    }
}

#[tonic::async_trait]
impl rpc_server::config_monitor_a_server::ConfigMonitorA for RpcServerImpl {
    /// agent提供的rpc接口，server端调用此接口向agent同步配置文件
    /// agent端接收到请求后，将请求中的文件覆写到本地文件
    async fn sync_config(
        &self,
        request: Request<rpc_server::SyncConfigRequest>,
    ) -> Result<Response<rpc_server::SyncConfigResponse>, Status> {
        #[cfg(debug_assertions)]
        println!("Got a sync request from _server{:?}", request.remote_addr());

        let file_path = request.get_ref().config_file_path_agent.clone();
        let file_content = request.get_ref().config_file_content.clone();
        #[cfg(debug_assertions)]
        println!("!file_path: {}, payload: \"{}\"", file_path, file_content);

        // 将配置文件内容覆盖写入本地文件

        // 如果路径不存在则创建
        if let Err(e) =
            tokio::fs::create_dir_all(PathBuf::from(file_path.clone()).parent().unwrap()).await
        {
            return Err(Status::internal(e.to_string()));
        }

        // 写入文件
        if let Err(e) = tokio::fs::write(file_path.clone(), file_content).await {
            return Err(Status::internal(e.to_string()));
        }

        // 把这个file添加到config_files
        {
            let mut files = self.config_files.lock().await;
            if !files.contains(&file_path) {
                println!("insert file_path: {}", file_path);
                files.insert(file_path.clone());
                self.audit_new_rule(file_path.clone()).await.unwrap();
            }
        }

        let mut resp = rpc_server::SyncConfigResponse::default();

        // apply command
        // 在/tmp/ConfigMoniter/apply_command.json中查找是否有对应的命令，如果有则执行value
        const APPLY_COMMAND_PATH: &str = "/tmp/ConfigMoniter/apply_command.json";
        if let Ok(contents) = tokio::fs::read_to_string(APPLY_COMMAND_PATH).await {
            let json: serde_json::Value = serde_json::from_str(&contents).unwrap();
            if let Some(value) = json.get(&file_path) {
                if let Some(value) = value.as_str() {
                    #[cfg(debug_assertions)]
                    println!("file_path: {}, apply_command: {}", file_path, value);

                    match tokio::process::Command::new("sh")
                        .arg("-c")
                        .arg(value)
                        .output()
                        .await
                    {
                        Ok(output) => {
                            resp.success = true;
                            resp.message = format!("{:?}", output);
                        }
                        Err(e) => {
                            resp.success = false;
                            resp.message = format!("{:?}", e);
                        }
                    }
                }
            }
        } else {
            resp.success = true;
            resp.message = "success, no apply command".to_string();
        }

        #[cfg(debug_assertions)]
        println!("resp: {:?}", resp);

        Ok(Response::new(resp))
    }
}

/// audit相关的实现
impl RpcServerImpl {
    /// 创建一个新的审计规则，监控指定文件，返回规则的key
    async fn audit_new_rule(&self, file_path: String) -> anyhow::Result<()> {
        self.audit_new_rule_auditctl(file_path).await
    }
    const AUDIT_RULE_KEY_PREFIX: &'static str = "CM_";
    async fn audit_new_rule_auditctl(&self, file_path: String) -> anyhow::Result<()> {
        // sudo auditctl -w {file_path} -F perm=wa -F pid!={self.pid} -F key={rule_key}
        let output = tokio::process::Command::new("sudo")
            .arg("auditctl")
            .arg("-w")
            .arg(file_path.clone())
            .arg("-F")
            .arg("perm=wa")
            .arg("-F")
            .arg(format!("pid!={}", self.pid))
            .arg("-F")
            .arg(format!(
                "key={}",
                Self::AUDIT_RULE_KEY_PREFIX.to_owned() + &file_path
            ))
            .output()
            .await?;
        if !output.status.success() {
            println!("output: {:?}", output);
            anyhow::bail!("Failed to add audit rule");
        }
        Ok(())
    }
}
